/* 
 * File:   OperacionDescargaArchivo.cpp
 * Author: maximiliano
 * 
 * Created on 16 de junio de 2013, 00:10
 */

#include <fstream>
#include <exception>
#include <stdint.h>
#include "common_Codec.h"
#include "common_NumeroUtils.h"
#include "client_OperacionDescargaArchivo.h"

using namespace std;
using namespace TP;
using namespace TP::Operaciones;

#define ARCHIVO_NO_DECLARADO 1
#define ERROR_IO 2
#define ERROR_INTEGRIDAD_REPOSITORIO 3
        
OperacionDescargaArchivo::OperacionDescargaArchivo( uint32_t pToken
                                                  , FileServiceProxy* pFileService
                                                  , Repositorio* pRepositorio
                                                  , const string &pNombreArchivo
                                                  , const uint32_t &pVersionArchivo
                                                  ) : Operacion (pToken)
{
    _repositorio = pRepositorio;
    _fileService = pFileService;
    _nombreArchivo = pNombreArchivo;
    _versionArchivo = pVersionArchivo;
}

OperacionDescargaArchivo::~OperacionDescargaArchivo() {
    
}

void OperacionDescargaArchivo::ejecutar(){
    
    try
    {
        // Verifico la validez de la operacion.
        
        RepoItem* item = _repositorio->GetRepoItemByFilename(_nombreArchivo);
        if (NULL == item) throw ARCHIVO_NO_DECLARADO;
        if (item->GetEstado() != ARCHIVO_A_DESCARGAR) throw ERROR_INTEGRIDAD_REPOSITORIO;
        
        // Declaro el archivo fisico.
        if (!_fileUtils.CrearArchivo (item->GetRutaCompletaArchivo())) throw ERROR_IO;
        
        // Inicia la descarga del archivo.
        this->getChunkDelServidor (0);
    }
    catch (const int &ex)
    {
        if (ARCHIVO_NO_DECLARADO == ex)
        {
            cerr << "Se configuro la descarga de un archivo desde el servidor "
                    "Sin estar declarado en el repositorio."
                << endl;
            
        } else if (ERROR_IO == ex)
        {
            cerr << "Error al iniciar en el repositorio local el archivo a descargar." << endl;
        }
        else if (ERROR_INTEGRIDAD_REPOSITORIO == ex)
        {
            cerr << "El estado del archivo en el respositorio no se corresponde"
                    "con el estado esperado de archivo a descargar." << endl;
        }
        this->setFinalizada(true);
    }
}

void OperacionDescargaArchivo::procesarRespuesta (vector<string> &pRespuesta){
    
    /* Formato respuesta:
     * <token>;<recurso>;<accion>;<resultado>;<args>
     */
    
    if (FileServiceConstants::GET_CHUNK == pRespuesta[2])
    {
        this->manejarRespuestaGetChunkDelServidor(pRespuesta);
    }
    
}

void OperacionDescargaArchivo::getChunkDelServidor(const uint32_t &pNumeroChunk){
    
    _fileService->GetFileChunk(getId(), _nombreArchivo, pNumeroChunk);
}

void OperacionDescargaArchivo::manejarRespuestaGetChunkDelServidor (vector<string> &pRespuesta){
    
    /* El archivo existe y el hash solicitado corresponde con el archivo
     * indicado.  se comunica el chunk.
     * 
     * Formato de la respuesta:
     * 
     * <token>(0);<recurso>(1);<accion>(2);<resultado>(3);
     *      <nombre_archivo>
     * ;    <numero_chunk>
     * ;    <hash>
     * ;    <chunk>
     * 
     */
    
    try
    {
        if (FileServiceConstants::ERROR == pRespuesta[3])
        {
            throw pRespuesta[5];
        }        

        int32_t numeroChunk = NumeroUtils::ParseInt32(pRespuesta[5]);
        
        if (-1 == numeroChunk)
        {
            string nombreArchivo = Codec::decodificar(pRespuesta[4]);
            RepoItem *item = _repositorio->GetRepoItemByFilename(nombreArchivo);
            item->SetEstado(ARCHIVO_SINCRONIZADO);            
            
            this->setFinalizada(true);
            return;
        }
        
        string nombreArchivo = Codec::decodificar(pRespuesta[4]);
        RepoItem *item = _repositorio->GetRepoItemByFilename(nombreArchivo);
        
        if (NULL == item)
        {
            throw "El archivo al que hace referencia la notficacion del servidor no posee archivo asociado en el repositorio local. Archivo " +  nombreArchivo;
        }

        // Guardo el chunk y actualizo le repositorio.

        this->GuardarChunkEnArchivo (item->GetRutaCompletaArchivo(), pRespuesta[7]);
        item->AddHash(pRespuesta[6]);

        // inicio la descarga del siguiente chunk.

        this->getChunkDelServidor(++numeroChunk);
    }
    catch (const string &ex)
    {
        cerr << "No se puede descargar el archivo " << _nombreArchivo << ". " << ex << endl;
        this->setFinalizada(true);
    }
}

void OperacionDescargaArchivo::GuardarChunkEnArchivo (const string& pRutaCompletaArchivo, const string &pChunk){

    // Decodifico el chunk.
    Codec codec;
    uint8_t* chunk = NULL;
    uint32_t longitudChunk = 0;
    
    codec.decodificar(chunk, longitudChunk, pChunk);
    
    // Guardo el chunk en el archivo indicado.
    if (longitudChunk > 0)
    {
        ofstream file (pRutaCompletaArchivo.c_str(), ios::ate);
        if (file.is_open())
        {
            file.write((char*) chunk, longitudChunk);
        }
    }
}
